{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vX-FA27MbYpQ"
      },
      "source": [
        "# Search Wikipedia using ReAct"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Pk4Y-PKWc3MU"
      },
      "source": [
        "<table align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/google-gemini/cookbook/blob/main/examples/Search_Wikipedia_using_ReAct.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Run in Google Colab</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sdkuZY1IdRal"
      },
      "source": [
        "This notebook is a minimal implementation of [ReAct: Synergizing Reasoning and Acting in Language Models](https://arxiv.org/abs/2210.03629) with the Google `gemini-pro` model. You'll use ReAct prompting to configure a model to search Wikipedia to find the answer to a user's question.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PSr-BK-5meRo"
      },
      "source": [
        "In this walkthrough, you will learn how to:\n",
        "\n",
        "\n",
        "1.   Set up your development environment and API access to use Gemini.\n",
        "2.   Use a ReAct few-shot prompt.\n",
        "3.   Use the newly prompted model for multi-turn conversations (chat).\n",
        "4.   Connect the model to the **Wikipedia API**.\n",
        "5.  Have conversations with the model (try asking it questions like \"how tall is the Eiffel Tower?\") and watch it search Wikipedia.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ce4xbIDDXP7L"
      },
      "source": [
        "> Note: The non-source code materials on this page are licensed under Creative Commons - Attribution-ShareAlike CC-BY-SA 4.0, https://creativecommons.org/licenses/by-sa/4.0/legalcode."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lSkx3VHr3WYb"
      },
      "source": [
        "### Background\n",
        "\n",
        "  \n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PqoT0ojAcV9P"
      },
      "source": [
        "[ReAct](https://arxiv.org/abs/2210.03629) is a prompting method which allows language models to create a trace of their thinking processes and the steps required to answer a user's questions. This improves human interpretability and trustworthiness. ReAct prompted models generate Thought-Action-Observation triplets for every iteration, as you'll soon see. Let's get started!"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "cVvxnBG-thZG"
      },
      "source": [
        "## Setup\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Twc_XZ7h7Bb4"
      },
      "outputs": [],
      "source": [
        "!pip install -q google.generativeai"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7oZwkgQpfrLl"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "  Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
            "  Building wheel for wikipedia (setup.py) ... \u001b[?25l\u001b[?25hdone\n"
          ]
        }
      ],
      "source": [
        "!pip install -q wikipedia"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DVWIqdtbffau"
      },
      "source": [
        "Note: The [`wikipedia` package](https://pypi.org/project/wikipedia/) notes that it was \"designed for ease of use and simplicity, not for advanced use\", and that production or heavy use should instead \"use [Pywikipediabot](http://www.mediawiki.org/wiki/Manual:Pywikipediabot) or one of the other more advanced [Python MediaWiki API wrappers](http://en.wikipedia.org/wiki/Wikipedia:Creating_a_bot#Python)\"."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Jz5HOLy47VX0"
      },
      "outputs": [],
      "source": [
        "import re\n",
        "import os\n",
        "\n",
        "import wikipedia\n",
        "from wikipedia.exceptions import DisambiguationError, PageError\n",
        "\n",
        "import google.generativeai as genai"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "SAvjxTybuWw-"
      },
      "source": [
        "To run the following cell, your API key must be stored it in a Colab Secret named `GOOGLE_API_KEY`. If you don't already have an API key, or you're not sure how to create a Colab Secret, see the [Authentication](https://github.com/google-gemini/cookbook/blob/main/quickstarts/Authentication.ipynb) quickstart for an example."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JAzIedGr9PdN"
      },
      "outputs": [],
      "source": [
        "from google.colab import userdata\n",
        "GOOGLE_API_KEY=userdata.get('GOOGLE_API_KEY')\n",
        "genai.configure(api_key=GOOGLE_API_KEY)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Sqkwp87FumIp"
      },
      "source": [
        "## The ReAct prompt"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lLv9Kuuu5Ffs"
      },
      "source": [
        "The prompts used in the paper are available at [https://github.com/ysymyth/ReAct/tree/master/prompts](https://github.com/ysymyth/ReAct/tree/master/prompts)\n",
        "\n",
        "Here, you will be working with the following ReAct prompt with a few minor adjustments."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "07ed55c29a1d"
      },
      "source": [
        "> Note: The prompt and in-context examples used here are borrowed from [https://github.com/ysymyth/ReAct](https://github.com/ysymyth/ReAct) which is published under a [MIT license](https://opensource.org/licenses/MIT)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "g8klL8df4iXe"
      },
      "outputs": [],
      "source": [
        "model_instructions = \"\"\"Solve a question answering task with interleaving Thought, Action, Observation steps. Thought can reason about the current situation, Observation is understanding relevant information from an Action's output and Action can be of three types:\n",
        "(1) <search>entity</search>, which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search and you can try to search the information from those topics.\n",
        "(2) <lookup>keyword</lookup>, which returns the next sentence containing keyword in the current context. This only does exact matches, so keep your searches short.\n",
        "(3) <finish>answer</finish>, which returns the answer and finishes the task.\n",
        "\"\"\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Fw52CHAG0aRr"
      },
      "source": [
        "### Few-shot prompting to enable in-context learning with Gemini\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-jhaD4ChNv6M"
      },
      "source": [
        "While large language models show good understanding of the instructions they are prompted with, they still may perform poorly on complex tasks in a zero-shot setting. Hence, you will now provide a few examples along with your prompt to steer the model's output according to your needs. This in-context learning improves the model's performance significantly."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "tZ7vezr02qv0"
      },
      "outputs": [],
      "source": [
        "examples = \"\"\"\n",
        "Here are some examples.\n",
        "\n",
        "Question\n",
        "What is the elevation range for the area that the eastern sector of the Colorado orogeny extends into?\n",
        "\n",
        "Thought 1\n",
        "I need to search Colorado orogeny, find the area that the eastern sector of the Colorado orogeny extends into, then find the elevation range of the area.\n",
        "\n",
        "Action 1\n",
        "<search>Colorado orogeny</search>\n",
        "\n",
        "Observation 1\n",
        "The Colorado orogeny was an episode of mountain building (an orogeny) in Colorado and surrounding areas.\n",
        "\n",
        "Thought 2\n",
        "It does not mention the eastern sector. So I need to look up eastern sector.\n",
        "\n",
        "Action 2\n",
        "<lookup>eastern sector</lookup>\n",
        "\n",
        "Observation 2\n",
        "The eastern sector extends into the High Plains and is called the Central Plains orogeny.\n",
        "\n",
        "Thought 3\n",
        "The eastern sector of Colorado orogeny extends into the High Plains. So I need to search High Plains and find its elevation range.\n",
        "\n",
        "Action 3\n",
        "<search>High Plains</search>\n",
        "\n",
        "Observation 3\n",
        "High Plains refers to one of two distinct land regions\n",
        "\n",
        "Thought 4\n",
        "I need to instead search High Plains (United States).\n",
        "\n",
        "Action 4\n",
        "<search>High Plains (United States)</search>\n",
        "\n",
        "Observation 4\n",
        "The High Plains are a subregion of the Great Plains. From east to west, the High Plains rise in elevation from around 1,800 to 7,000 ft (550 to 2,130m).\n",
        "\n",
        "Thought 5\n",
        "High Plains rise in elevation from around 1,800 to 7,000 ft, so the answer is 1,800 to 7,000 ft.\n",
        "\n",
        "Action 5\n",
        "<finish>1,800 to 7,000 ft</finish>\n",
        "\n",
        "Question\n",
        "Musician and satirist Allie Goertz wrote a song about the \"The Simpsons\" character Milhouse, who Matt Groening named after who?\n",
        "\n",
        "Thought 1\n",
        "The question simplifies to \"The Simpsons\" character Milhouse is named after who. I only need to search Milhouse and find who it is named after.\n",
        "\n",
        "Action 1\n",
        "<search>Milhouse</search>\n",
        "\n",
        "Observation 1\n",
        "Milhouse Mussolini Van Houten is a recurring character in the Fox animated television series The Simpsons voiced by Pamela Hayden and created by Matt Groening.\n",
        "\n",
        "Thought 2\n",
        "The paragraph does not tell who Milhouse is named after, maybe I can look up \"named after\".\n",
        "\n",
        "Action 2\n",
        "<lookup>named after</lookup>\n",
        "\n",
        "Observation 2\n",
        "Milhouse was named after U.S. president Richard Nixon, whose middle name was Milhous.\n",
        "\n",
        "Thought 3\n",
        "Milhouse was named after U.S. president Richard Nixon, so the answer is Richard Nixon.\n",
        "\n",
        "Action 3\n",
        "<finish>Richard Nixon</finish>\n",
        "\n",
        "Question\n",
        "Which documentary is about Finnish rock groups, Adam Clayton Powell or The Saimaa Gesture?\n",
        "\n",
        "Thought 1\n",
        "I need to search Adam Clayton Powell and The Saimaa Gesture, and find which documentary is about Finnish rock groups.\n",
        "\n",
        "Action 1\n",
        "<search>Adam Clayton Powell</search>\n",
        "\n",
        "Observation 1\n",
        "Could not find [Adam Clayton Powell]. Similar: [’Adam Clayton Powell III’, ’Seventh Avenue (Manhattan)’, ’Adam Clayton Powell Jr. State Office Building’, ’Isabel Washington Powell’, ’Adam Powell’, ’Adam Clayton Powell (film)’, ’Giancarlo Esposito’].\n",
        "\n",
        "Thought 2\n",
        "To find the documentary, I can search Adam Clayton Powell (film).\n",
        "\n",
        "Action 2\n",
        "<search>Adam Clayton Powell (film)</search>\n",
        "\n",
        "Observation 2\n",
        "Adam Clayton Powell is a 1989 American documentary film directed by Richard Kilberg. The film is about the rise and fall of influential African-American politician Adam Clayton Powell Jr.[3][4] It was later aired as part of the PBS series The American Experience.\n",
        "\n",
        "Thought 3\n",
        "Adam Clayton Powell (film) is a documentary about an African-American politician, not Finnish rock groups. So the documentary about Finnish rock groups must instead be The Saimaa Gesture.\n",
        "\n",
        "Action 3\n",
        "<finish>The Saimaa Gesture</finish>\n",
        "\n",
        "Question\n",
        "What profession does Nicholas Ray and Elia Kazan have in common?\n",
        "\n",
        "Thought 1\n",
        "I need to search Nicholas Ray and Elia Kazan, find their professions, then find the profession they have in common.\n",
        "\n",
        "Action 1\n",
        "<search>Nicholas Ray</search>\n",
        "\n",
        "Observation 1\n",
        "Nicholas Ray (born Raymond Nicholas Kienzle Jr., August 7, 1911 - June 16, 1979) was an American film director, screenwriter, and actor best known for the 1955 film Rebel Without a Cause.\n",
        "\n",
        "Thought 2\n",
        "Professions of Nicholas Ray are director, screenwriter, and actor. I need to search Elia Kazan next and find his professions.\n",
        "\n",
        "Action 2\n",
        "<search>Elia Kazan</search>\n",
        "\n",
        "Observation 2\n",
        "Elia Kazan was an American film and theatre director, producer, screenwriter and actor.\n",
        "\n",
        "Thought 3\n",
        "Professions of Elia Kazan are director, producer, screenwriter, and actor. So profession Nicholas Ray and Elia Kazan have in common is director, screenwriter, and actor.\n",
        "\n",
        "Action 3\n",
        "<finish>director, screenwriter, actor</finish>\n",
        "\n",
        "Question\n",
        "Which magazine was started first Arthur’s Magazine or First for Women?\n",
        "\n",
        "Thought 1\n",
        "I need to search Arthur’s Magazine and First for Women, and find which was started first.\n",
        "\n",
        "Action 1\n",
        "<search>Arthur’s Magazine</search>\n",
        "\n",
        "Observation 1\n",
        "Arthur’s Magazine (1844-1846) was an American literary periodical published in Philadelphia in the 19th century.\n",
        "\n",
        "Thought 2\n",
        "Arthur’s Magazine was started in 1844. I need to search First for Women next.\n",
        "\n",
        "Action 2\n",
        "<search>First for Women</search>\n",
        "\n",
        "Observation 2\n",
        "First for Women is a woman’s magazine published by Bauer Media Group in the USA.[1] The magazine was started in 1989.\n",
        "\n",
        "Thought 3\n",
        "First for Women was started in 1989. 1844 (Arthur’s Magazine) < 1989 (First for Women), so Arthur’s Magazine was started first.\n",
        "\n",
        "Action 3\n",
        "<finish>Arthur’s Magazine</finish>\n",
        "\n",
        "Question\n",
        "Were Pavel Urysohn and Leonid Levin known for the same type of work?\n",
        "\n",
        "Thought 1\n",
        "I need to search Pavel Urysohn and Leonid Levin, find their types of work, then find if they are the same.\n",
        "\n",
        "Action 1\n",
        "<search>Pavel Urysohn</search>\n",
        "\n",
        "Observation 1\n",
        "Pavel Samuilovich Urysohn (February 3, 1898 - August 17, 1924) was a Soviet mathematician who is best known for his contributions in dimension theory.\n",
        "\n",
        "Thought 2\n",
        "Pavel Urysohn is a mathematician. I need to search Leonid Levin next and find its type of work.\n",
        "\n",
        "Action 2\n",
        "<search>Leonid Levin</search>\n",
        "\n",
        "Observation 2\n",
        "Leonid Anatolievich Levin is a Soviet-American mathematician and computer scientist.\n",
        "\n",
        "Thought 3\n",
        "Leonid Levin is a mathematician and computer scientist. So Pavel Urysohn and Leonid Levin have the same type of work.\n",
        "\n",
        "Action 3\n",
        "<finish>yes</finish>\n",
        "\n",
        "Question\n",
        "{question}\"\"\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xeCImqiN3WiQ"
      },
      "source": [
        "Copy the instructions along with examples in a file called `model_instructions.txt`"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ZyTfAdpk26oB"
      },
      "outputs": [],
      "source": [
        "ReAct_prompt = model_instructions + examples\n",
        "with open('model_instructions.txt', 'w') as f:\n",
        "  f.write(ReAct_prompt)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Is8BIVQP3u95"
      },
      "source": [
        "## The Gemini-ReAct pipeline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "PqEwKVDgM1MF"
      },
      "source": [
        "### Setup"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "T4M3lxEoM3k0"
      },
      "source": [
        "You will now build an end-to-end pipeline to facilitate multi-turn chat with the ReAct-prompted Gemini model."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "vssDZcroN-Ob"
      },
      "outputs": [],
      "source": [
        "class ReAct:\n",
        "  def __init__(self, model: str, ReAct_prompt: str | os.PathLike):\n",
        "    \"\"\"Prepares Gemini to follow a `Few-shot ReAct prompt` by imitating\n",
        "    `function calling` technique to generate both reasoning traces and\n",
        "    task-specific actions in an interleaved manner.\n",
        "\n",
        "    Args:\n",
        "        model: name to the model.\n",
        "        ReAct_prompt: ReAct prompt OR path to the ReAct prompt.\n",
        "    \"\"\"\n",
        "    self.model = genai.GenerativeModel(model)\n",
        "    self.chat = self.model.start_chat(history=[])\n",
        "    self.should_continue_prompting = True\n",
        "    self._search_history: list[str] = []\n",
        "    self._search_urls: list[str] = []\n",
        "\n",
        "    try:\n",
        "      # try to read the file\n",
        "      with open(ReAct_prompt, 'r') as f:\n",
        "        self._prompt = f.read()\n",
        "    except FileNotFoundError:\n",
        "      # assume that the parameter represents prompt itself rather than path to the prompt file.\n",
        "      self._prompt = ReAct_prompt\n",
        "\n",
        "  @property\n",
        "  def prompt(self):\n",
        "    return self._prompt\n",
        "\n",
        "  @classmethod\n",
        "  def add_method(cls, func):\n",
        "    setattr(cls, func.__name__, func)\n",
        "\n",
        "  @staticmethod\n",
        "  def clean(text: str):\n",
        "    \"\"\"Helper function for responses.\"\"\"\n",
        "    text = text.replace(\"\\n\", \" \")\n",
        "    return text"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xKfThpmhMZYZ"
      },
      "source": [
        "### Define tools\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "dnvZ2jqdRHE1"
      },
      "source": [
        "As instructed by the prompt, the model will be generating **Thought-Action-Observation** traces, where every **Action** trace could be one of the following tokens:\n",
        "\n",
        "\n",
        "1.   </search/> : Perform a Wikipedia search via external API.\n",
        "2.   </lookup/> : Lookup for specific information on a page with the Wikipedia API.\n",
        "3.   </finish/> : Stop the execution of the model and return the answer.\n",
        "\n",
        "If the model encounters any of these tokens, the model should make use of the `tools` made available to the model. This understanding of the model to leverage acquired toolsets to collect information from the external world is often referred to as **function calling**. Therefore, the next goal is to imitate this function calling technique in order to allow ReAct prompted Gemini model to access the external groundtruth.\n",
        "\n",
        "The Gemini API supports function calling and you could use this feature to set up your tools. However, for this tutorial, you will learn to simulate it using `stop_sequences` parameter.\n",
        "\n",
        "\n",
        "Define the tools:"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ysHN4y4FPlJZ"
      },
      "source": [
        "#### Search\n",
        "Define a method to perform Wikipedia searches"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "yCRB4g4BNzak"
      },
      "outputs": [],
      "source": [
        "@ReAct.add_method\n",
        "def search(self, query: str):\n",
        "    \"\"\"Perfoms search on `query` via Wikipedia api and returns its summary.\n",
        "\n",
        "    Args:\n",
        "        query: Search parameter to query the Wikipedia API with.\n",
        "\n",
        "    Returns:\n",
        "        observation: Summary of Wikipedia search for `query` if found else\n",
        "        similar search results.\n",
        "    \"\"\"\n",
        "    observation = None\n",
        "    query = query.strip()\n",
        "    try:\n",
        "      # try to get the summary for requested `query` from the Wikipedia\n",
        "      observation = wikipedia.summary(query, sentences=4, auto_suggest=False)\n",
        "      wiki_url = wikipedia.page(query, auto_suggest=False).url\n",
        "      observation = self.clean(observation)\n",
        "\n",
        "      # if successful, return the first 2-3 sentences from the summary as model's context\n",
        "      observation = self.model.generate_content(f'Retun the first 2 or 3 \\\n",
        "      sentences from the following text: {observation}')\n",
        "      observation = observation.text\n",
        "\n",
        "      # keep track of the model's search history\n",
        "      self._search_history.append(query)\n",
        "      self._search_urls.append(wiki_url)\n",
        "      print(f\"Information Source: {wiki_url}\")\n",
        "\n",
        "    # if the page is ambiguous/does not exist, return similar search phrases for model's context\n",
        "    except (DisambiguationError, PageError) as e:\n",
        "      observation = f'Could not find [\"{query}\"].'\n",
        "      # get a list of similar search topics\n",
        "      search_results = wikipedia.search(query)\n",
        "      observation += f' Similar: {search_results}. You should search for one of those instead.'\n",
        "\n",
        "    return observation"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "v3fUbHUsPyoF"
      },
      "source": [
        "#### Lookup\n",
        "Look for a specific phrase on the Wikipedia page."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "_F4kAF77O0E_"
      },
      "outputs": [],
      "source": [
        "@ReAct.add_method\n",
        "def lookup(self, phrase: str, context_length=200):\n",
        "    \"\"\"Searches for the `phrase` in the lastest Wikipedia search page\n",
        "    and returns number of sentences which is controlled by the\n",
        "    `context_length` parameter.\n",
        "\n",
        "    Args:\n",
        "        phrase: Lookup phrase to search for within a page. Generally\n",
        "        attributes to some specification of any topic.\n",
        "\n",
        "        context_length: Number of words to consider\n",
        "        while looking for the answer.\n",
        "\n",
        "    Returns:\n",
        "        result: Context related to the `phrase` within the page.\n",
        "    \"\"\"\n",
        "    # get the last searched Wikipedia page and find `phrase` in it.\n",
        "    page = wikipedia.page(self._search_history[-1], auto_suggest=False)\n",
        "    page = page.content\n",
        "    page = self.clean(page)\n",
        "    start_index = page.find(phrase)\n",
        "\n",
        "    # extract sentences considering the context length defined\n",
        "    result = page[max(0, start_index - context_length):start_index+len(phrase)+context_length]\n",
        "    print(f\"Information Source: {self._search_urls[-1]}\")\n",
        "    return result"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Tc4mq2qlQCnE"
      },
      "source": [
        "#### Finish\n",
        "Instruct the pipline to terminate its execution."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0Wxpx8COPak_"
      },
      "outputs": [],
      "source": [
        "@ReAct.add_method\n",
        "def finish(self, _):\n",
        "  \"\"\"Finishes the conversation on encountering <finish> token by\n",
        "  setting the `self.should_continue_prompting` flag to `False`.\n",
        "  \"\"\"\n",
        "  self.should_continue_prompting = False\n",
        "  print(f\"Information Sources: {self._search_urls}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "u9Tl6W98Zhut"
      },
      "source": [
        "### Stop tokens and function calling imitation"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0VnX9zpBcdA0"
      },
      "source": [
        "Now that you are all set with function definitions, the next step is to instruct the model to interrupt its execution upon encountering any of the action tokens. You will make use of the `stop_sequences` parameter from [`genai.GenerativeModel.GenerationConfig`](https://ai.google.dev/api/python/google/generativeai/GenerationConfig) class to instruct the model when to stop. Upon encountering an action token, the pipeline will simply extract what specific token from the `stop_sequences` argument terminated the model's execution, and then call the appropriate **tool** (function).\n",
        "\n",
        "The function's response will be added to model's chat history for continuing the context link."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "vnQom1aQOsK8"
      },
      "outputs": [],
      "source": [
        "@ReAct.add_method\n",
        "def __call__(self, user_question, max_calls: int=8, **generation_kwargs):\n",
        "  \"\"\"Starts multi-turn conversation with the chat models with function calling\n",
        "\n",
        "  Args:\n",
        "      max_calls: max calls made to the model to get the final answer.\n",
        "\n",
        "      generation_kwargs: Same as genai.GenerativeModel.GenerationConfig\n",
        "              candidate_count: (int | None) = None,\n",
        "              stop_sequences: (Iterable[str] | None) = None,\n",
        "              max_output_tokens: (int | None) = None,\n",
        "              temperature: (float | None) = None,\n",
        "              top_p: (float | None) = None,\n",
        "              top_k: (int | None) = None\n",
        "\n",
        "  Raises:\n",
        "      AssertionError: if max_calls is not between 1 and 8\n",
        "  \"\"\"\n",
        "\n",
        "  # hyperparameter fine-tuned according to the paper\n",
        "  assert 0 < max_calls <= 8, \"max_calls must be between 1 and 8\"\n",
        "\n",
        "  if len(self.chat.history) == 0:\n",
        "    model_prompt = self.prompt.format(question=user_question)\n",
        "  else:\n",
        "    model_prompt = user_question\n",
        "\n",
        "  # stop_sequences for the model to immitate function calling\n",
        "  callable_entities = ['</search>', '</lookup>', '</finish>']\n",
        "\n",
        "  generation_kwargs.update({'stop_sequences': callable_entities})\n",
        "\n",
        "  self.should_continue_prompting = True\n",
        "  for idx in range(max_calls):\n",
        "\n",
        "    self.response = self.chat.send_message(content=[model_prompt],\n",
        "              generation_config=generation_kwargs, stream=False)\n",
        "\n",
        "    for chunk in self.response:\n",
        "      print(chunk.text, end=' ')\n",
        "\n",
        "    response_cmd = self.chat.history[-1].parts[-1].text\n",
        "\n",
        "    try:\n",
        "      # regex to extract <function name writen in between angular brackets>\n",
        "      cmd = re.findall(r'<(.*)>', response_cmd)[-1]\n",
        "      print(f'</{cmd}>')\n",
        "      # regex to extract param\n",
        "      query = response_cmd.split(f'<{cmd}>')[-1].strip()\n",
        "      # call to appropriate function\n",
        "      observation = self.__getattribute__(cmd)(query)\n",
        "\n",
        "      if not self.should_continue_prompting:\n",
        "        break\n",
        "\n",
        "      stream_message = f\"\\nObservation {idx + 1}\\n{observation}\"\n",
        "      print(stream_message)\n",
        "      # send function's output as user's response\n",
        "      model_prompt = f\"<{cmd}>{query}</{cmd}>'s Output: {stream_message}\"\n",
        "\n",
        "    except (IndexError, AttributeError) as e:\n",
        "      model_prompt = \"Please try to generate thought-action-observation traces \\\n",
        "      as instructed by the prompt.\""
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xtndhebkhW62"
      },
      "source": [
        "### Test ReAct prompted Gemini model"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "h_KWkXWwfZ5h"
      },
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "ERROR:tornado.access:503 POST /v1beta/models/gemini-pro:generateContent?%24alt=json%3Benum-encoding%3Dint (127.0.0.1) 2107.88ms\n",
            "ERROR:tornado.access:503 POST /v1beta/models/gemini-pro:generateContent?%24alt=json%3Benum-encoding%3Dint (127.0.0.1) 1876.20ms\n"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Action 1\n",
            "<search>Percy Jackson and the Olympians TV series </search>\n",
            "\n",
            "Observation 1\n",
            "Could not find [\"Percy Jackson and the Olympians TV series\"]. Similar: ['Percy Jackson and the Olympians (TV series)', 'Percy Jackson & the Olympians', 'Percy Jackson (film series)', 'Percy Jackson & the Olympians: The Lightning Thief', 'Percy Jackson (disambiguation)', 'Percy Jackson', 'List of characters in mythology novels by Rick Riordan', 'The Lightning Thief', 'The Heroes of Olympus', 'Walker Scobell']. You should search for one of those instead.\n",
            "Action 2\n",
            "<search>Percy Jackson and the Olympians (TV series) </search>\n",
            "Information Source: https://en.wikipedia.org/wiki/Percy_Jackson_and_the_Olympians_(TV_series)\n",
            "\n",
            "Observation 2\n",
            "Percy Jackson and the Olympians is an American fantasy television series created by Rick Riordan and Jonathan E. Steinberg for Disney+, based on the book series of the same name by Riordan. Walker Scobell stars as Percy Jackson, alongside Leah Sava Jeffries and Aryan Simhadri. \n",
            "\n",
            "Development on the series began in May 2020, following a pitch by Riordan to Disney Branded Television.\n"
          ]
        },
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "ERROR:tornado.access:503 POST /v1beta/models/gemini-pro:generateContent?%24alt=json%3Benum-encoding%3Dint (127.0.0.1) 433.49ms\n"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Thought 1\n",
            "I need to find the ages of Walker Scobell, Leah Sava Jeffries, and Aryan Simhadri, then sum them up.\n",
            "\n",
            "Action 3\n",
            "<search>Walker Scobell </search>\n"
          ]
        },
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "ERROR:tornado.access:503 POST /v1beta/models/gemini-pro:generateContent?%24alt=json%3Benum-encoding%3Dint (127.0.0.1) 410.12ms\n",
            "ERROR:tornado.access:503 POST /v1beta/models/gemini-pro:generateContent?%24alt=json%3Benum-encoding%3Dint (127.0.0.1) 460.87ms\n"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Information Source: https://en.wikipedia.org/wiki/Walker_Scobell\n",
            "\n",
            "Observation 3\n",
            "Walker Scobell (born January 5, 2009) is an American actor. He has starred in the 2022 action comedy films The Adam Project and Secret Headquarters. In 2023, he began playing the title character of Percy Jackson in the Disney+ fantasy series Percy Jackson and the Olympians.\n",
            "Thought 2\n",
            "Walker Scobell was born on January 5, 2009. I need to find the ages of Leah Sava Jeffries and Aryan Simhadri next.\n",
            "\n",
            "Action 4\n",
            "<search>Leah Sava Jeffries </search>\n",
            "Information Source: https://en.wikipedia.org/wiki/Leah_Jeffries\n",
            "\n",
            "Observation 4\n",
            "Leah Sava Jeffries is an American child actress born on September 25, 2009. She made her acting debut in the American musical drama, Empire (2015) and later her feature film debut in the action-thriller Beast (2022).\n",
            "Thought 3\n",
            "Leah Sava Jeffries was born on September 25, 2009. I need to find the age of Aryan Simhadri next.\n",
            "\n",
            "Action 5\n",
            "<search>Aryan Simhadri </search>\n",
            "Information Source: https://en.wikipedia.org/wiki/Aryan_Simhadri\n",
            "\n",
            "Observation 5\n",
            "Aryan Simhadri, born May 6, 2006, is an American actor of Indian descent, best known for his role as Grover Underwood in the Disney+ series Percy Jackson and the Olympians. In 2021, Simhadri starred in the off-Broadway production of Trevor: The Musical as Walter.\n",
            "Thought 4\n",
            "Aryan Simhadri was born on May 6, 2006. So the total of ages of the main trio from the new Percy Jackson and the Olympians TV series in real life is 14 + 13 + 16 = 43.\n",
            "\n",
            "Action 6\n",
            "<finish>43 </finish>\n",
            "Information Sources: ['https://en.wikipedia.org/wiki/Percy_Jackson_and_the_Olympians_(TV_series)', 'https://en.wikipedia.org/wiki/Walker_Scobell', 'https://en.wikipedia.org/wiki/Leah_Jeffries', 'https://en.wikipedia.org/wiki/Aryan_Simhadri']\n"
          ]
        }
      ],
      "source": [
        "gemini_ReAct_chat = ReAct(model='gemini-pro', ReAct_prompt='model_instructions.txt')\n",
        "# Note: try different combinations of generational_config parameters for variational results\n",
        "gemini_ReAct_chat(\"What are the total of ages of the main trio from the new Percy Jackson and the Olympians TV series in real life?\", temperature=0.2)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZIfeyyI6hoIE"
      },
      "source": [
        "Now, try asking the same question to `gemini-pro` model without the ReAct prompt."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "_NUXNbTuakSC"
      },
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "ERROR:tornado.access:503 POST /v1beta/models/gemini-pro:generateContent?%24alt=json%3Benum-encoding%3Dint (127.0.0.1) 584.37ms\n"
          ]
        },
        {
          "data": {
            "application/vnd.google.colaboratory.intrinsic+json": {
              "type": "string"
            },
            "text/plain": [
              "\"The main trio from the new Percy Jackson and the Olympians TV series haven't been cast yet, so their real-life ages are unknown.\""
            ]
          },
          "execution_count": 16,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "gemini_ReAct_chat.model.generate_content(\"What is the total of ages of the main trio from the new Percy Jackson and the Olympians TV series in real life?\").text"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "B-jsJSyBtrP8"
      },
      "source": [
        "## Summary\n",
        "\n",
        "The ReAct prompted Gemini model is grounded by external information sources and hence is less prone to hallucination. Furthermore, Thought-Action-Observation  traces generated by the model enhance human interpretability and trustworthiness by allowing users to witness the model's reasoning process for answering the user's query.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "vmdNYTm5Lobz"
      },
      "source": [
        "## Next steps\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iTiDOoTkLvH6"
      },
      "source": [
        "Head over to this [Streamlit app](https://mayochat.streamlit.app/) to interact with a ReAct prompted Gemini bot built with this code."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "name": "Search_Wikipedia_using_ReAct.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
